import java.util.ArrayList;

import javax.sound.midi.Receiver;

import dataModifiers.Modifier;

import inputs.Color;
import inputs.Input;
import inputs.PixelInput;
import management.MidiManager;
import midi.PitchVelocityMidiOutParameter;
import outputAdapters.OutputAdapter;
import outputAdapters.OutputRanges;
import processing.core.PFont;
import processingHandling.WebCamPAppletWrapper;


/**
 * A simple example of how to setup a processing applet that takes webcam data,
 * modifies it, and passes it to MidiYoke with the intention of being interpreted
 * by the example BuzzTracks.
 * 
 * February 4, 2010
 * 
 * @author Dylan James
 *
 */
public class SampleProgram extends WebCamPAppletWrapper{
	PixelInput middlePixel; // Pixel Input to get the pixel data at the middle of the screen.

	Modifier middlePixelRedPlusBlueMod; // Modifier to find the sum of the Red and Blue values of the middle pixel.
	Modifier middlePixelRGBSumMod; // Modifier to find the sum of the RGB values of the middle pixel.
	Modifier middlePixelRGBAverageMod; // Modifier to find the average of the RGB values of the middle pixel.
	Modifier middlePixelRGBSumHighPassMod; // High Pass modifier of the middle pixel value.
	
	OutputAdapter middlePixelRBSumPitchScaleAdapter; // Scale Adapter to scale the RB Sum into a suitable Midi Pitch value.
	OutputAdapter middlePixelRGBSumPitchScaleAdapter; // Scale Adapter to scale the RGB Sum into a suitable Midi Pitch value.
	
	OutputAdapter middlePixelRGBAvgPitchScaleAdapter; // Scale Adapter to scale the RGB Average into a suitable Midi Pitch value.
	OutputAdapter middlePixelRGBSumHPVelocityScaleAdapter; // Scale Adapter to scale the RGB Sum High Pass into a suitable Midi Veloctiy value.
	
	
	PitchVelocityMidiOutParameter dronePitchVelOut; // Midi Out Parameter to drive the Drone's Pitch and Velocity;
	PitchVelocityMidiOutParameter arpPitchVelOut; // Midi Out Parameter to drive the Arpeggio's Pitch and Velocity;
	PitchVelocityMidiOutParameter harmPitchVelOut; // Midi Out Parameter to drive the Harmony's Pitch and Velocity;
	
	Receiver midiYoke1Rec; // Midi Receiver used to write to MidiYoke1
	
	public void Setup() {
		
		
		// ***********
		// Input Setup
		// ***********
		// Here we setup all the inputs we'll be using to acquire data during this Applet.
		
		// ******************
		// Middle Pixel Input
		// ******************
		// We want a PixelInput for the middle of the capture to make its data easily accessible.
		
		
		// Setup the middle pixel. Set location to the middle.
		// We use TheInputFactory to create Inputs, as it manages updates and invalidations for us at each tick.
		// The true parameter tells TheInputFactory that it should be updated automatically each tick.
		// Leaving out this parameter will default it to true.
		middlePixel = TheInputFactory.createPixelInput(this.getWidth()/2, this.getHeight()/2, true);
		
		
		
		// ***********************
		// Middle Pixel Red + Blue
		// ***********************
		// We want a sum modifier to sum the Red and Blue values at the middle pixel.
		
		// Modifiers take a list of inputs.
		// Create a local list of inputs for our red and blue sum.
		// 2 tells it to only allocate space for 2 Inputs, as we know this is all we'll be using.
		ArrayList<Input> middlePixelRedAndBlueInputList = new ArrayList<Input>(2);
		
		// Add each color to the input list
		// We ask the middlePixel for its red and blue inputs, and add each one.
		middlePixelRedAndBlueInputList.add(middlePixel.getRedInput());
		middlePixelRedAndBlueInputList.add(middlePixel.getBlueInput());
		
		// Setup a modifier for the middle pixels red + blue sum.
		// We use TheModifierFactory just like the input factory, but for modifiers instead.
		middlePixelRedPlusBlueMod = TheModifierFactory.createSum(middlePixelRedAndBlueInputList, true);
		
		
		// *******************************
		// Middle Pixel Red + Blue + Green
		// *******************************
		// We want a sum modifier to sum the Red and Blue and Green values at the middle pixel.
		
		// We already have the red and blue summed, so we can reuse that or get all three inputs again.
		// Here we'll go with the first choice, just to demonstrate reusing modifiers as inputs to another modifier.
		
		// Create a local list of inputs for our RGB Sum.
		ArrayList<Input> middlePixelRedBlueGreenInputList = new ArrayList<Input>(2);
		
		// Add each input.
		// First we'll add the old Red + Blue sum.
		middlePixelRedBlueGreenInputList.add(middlePixelRedPlusBlueMod);
		// Now we'll add the green value by asking the middle pixel for its green input.
		middlePixelRedBlueGreenInputList.add(middlePixel.getGreenInput());
		
		// Ask TheModifierFactory for another Sum Modifier based off the given list, and to update it automatically.
		middlePixelRGBSumMod = TheModifierFactory.createSum(middlePixelRedBlueGreenInputList, true);
		
		
		// *************************************
		// Middle Pixel Red, Blue, Green Average
		// *************************************
		// We want an average modifier to average the Red, Green, and Blue values at the middle pixel.
		// We want this to be a running average, so it averages it over a the entire program, instead of just the current tick.
		
		// If we had gone with the second choice above, and made a new list of all three colors inputs, we could reuse it here...
		// Instead we'll have to make a new one - the old one summed red and green.
		ArrayList<Input> middlePixelRGBAverageList = new ArrayList<Input>(3);
		
		// Add each input. Ask the middlePixel for each colors input and add it.
		middlePixelRGBAverageList.add(middlePixel.getRedInput());
		middlePixelRGBAverageList.add(middlePixel.getGreenInput());
		middlePixelRGBAverageList.add(middlePixel.getBlueInput());
		
		// Get an average modifier from TheModifierFactory based off the given inputs, and update it automatically.
		middlePixelRGBAverageMod = TheModifierFactory.createAverage(middlePixelRGBAverageList, true);
		
		// Tell the Average Modifier to be a running average.
		middlePixelRGBAverageMod.setRun(true);
		
		
		// *****************************************
		// Middle Pixel Red + Blue + Green High Pass
		// *****************************************
		// We want a High Pass modifier that outputs outputs the RGB Sum of the middle pixel
		// only if that sum is greater than 150. Otherwise, it outputs 0.
		
		// High Pass modifiers use a list of inputs to represent their parameters.
		// The first item in the list is the cutoff of the High Pass - 150 in our case.
		// The second item in the list is the value to test and output if it passes - the RGB sum in our case.
		// Note that this second item is the middlePixelRGBSumMod from above.
		
		// Create a list to use as input to the HighPass
		ArrayList<Input> middlePixelRGBSumHighPassInputList = new ArrayList<Input>(2);
		
		// *************************
		// **** First item - the 150
		// *************************
		// The list takes Input objects. To be able to add this 150 to the list, we use a Constant Input object.
		// These objects just take a value, and always output that value, until they are told to change it.
		// Request on from TheInputFactory, and add it to the list.
		// Since it is a Constant, it doesn't need to be updated.
		middlePixelRGBSumHighPassInputList.add(TheInputFactory.createConstant(150, false));
		
		// ******************************
		// **** Second item - the RGB Sum
		// ******************************
		// We already have this from above, so just pass it along
		middlePixelRGBSumHighPassInputList.add(middlePixelRGBSumMod);
		
		
		
		// Now we'll actually get the modifier from the factory based off this list.
		middlePixelRGBSumHighPassMod = TheModifierFactory.createHighPass(middlePixelRGBSumHighPassInputList, true);
		
		
		
		
		
		
		
		// ********************
		// Output Adapter Setup
		// ********************
		// Here we'll setup all the Output Adapters we'll be using to fit our data into an acceptable Midi format.
		
		
		// ***********************************
		// Middle Red + Blue Sum -> Midi Pitch
		// ***********************************
		// We want to use the Red + Blue sum to drive a Midi Pitch, but it needs to be adapted into a Midi value.
		// We will use a simple scale adapter here.
		// Scale adapters basically just scale their input value to an output value by comparing the range of input
		// compared to the range of the output.
		//    For this example, Red(0-255) + Blue(0-255) = a value between 0 and 510.
		//	  Midi can only do 0-127, so well divide the summed value by 510, and multiply it by 127 to get our output value.
		
		// We will use TheOutputAdapterFactory to create a ScaleAdapter, just like we use the Modifier and Input factories.
		// This will, again, take care of updates and invalidation for us.
		
		// ScaleAdapters take a range to scale to, and an input to scale.
		// The OutputRanges enum provides common ranges, including the Midi Pitch, so we simply pass that.
		// The input will be the Red + Blue sum Modifier we created above.
		middlePixelRBSumPitchScaleAdapter = TheOutputAdapterFactory.createScaleAdapter(OutputRanges.Pitch, middlePixelRedPlusBlueMod);
		
		
		// *******************************************
		// Middle Red + Blue + Green Sum -> Midi Pitch
		// *******************************************
		// Almost exactly the same as the last one, except this time we will use the RGB total, instead of just Red and Blue.
		// This gives us a much different input range, but because of the way the systems designed, the ScaleAdapter will
		// automatically recognize this difference, and scale everything accordingly. :D
		
		middlePixelRGBSumPitchScaleAdapter = TheOutputAdapterFactory.createScaleAdapter(OutputRanges.Pitch, middlePixelRGBSumMod);
		
		
		// *********************************************
		// Middle Red, Blue, Green Average -> Midi Pitch
		// *********************************************
		// Again, we're scaling a modifier into a pitch. But two differences:
		// 	1) This is an average so the input range will be different.
		//	2) This is a RUNNING average.
		// Again, the ScaleAdapter will magically take care of all of this for us, and give us a usable output.
		// Even if we used a running sum inside averages inside sums or whatever scheme we could layout, the ScaleAdapter will
		// Recognize this and take care of the math for us. This lets us focus on implementing a pattern without having to worry
		// about the math we'd have to do to fit it into place.

		middlePixelRGBAvgPitchScaleAdapter = TheOutputAdapterFactory.createScaleAdapter(OutputRanges.Pitch, middlePixelRGBAverageMod);
		
		
		// ************************************************************
		// Middle (Red + Blue + Green Sum) HighPass(150) - > Midi Velocity
		// ************************************************************
		// Here we're scaling the High Passed RGB Sum into a Midi Velocity.
		// This is almost exactly identical to the above.
		
		middlePixelRGBSumHPVelocityScaleAdapter = TheOutputAdapterFactory.createScaleAdapter(OutputRanges.Velocity, middlePixelRGBSumHighPassMod);
		
		
		
		
		// **************
		// Midi Out Setup
		// **************
		// Here we'll actually bind the output adapters to the proper Midi Outs that we use to send Midi data to Buzz.
		
		
		// ***********************
		// Drone - Pitch, Velocity
		// ***********************
		// Here we want to link Output Adapters to the Drone Pitch and Velocity parameters.
		// The pitch will come from the Red + Blue Sum Scale Adapter we created above.
		// The velocity will be a constant of 60, but needs to be an input, so we will create a Constant Input.
		Input droneVelocity = TheInputFactory.createConstant(60, false);
		
		// The MidiOut Parameter also takes a Midi Receiver to write to. We get this through the Midi Manager.
		// Right now we're only using Midi Yoke 1, though this may change in the future if we need more channels.
		midiYoke1Rec = TheMidiManager.getMidiYoke1Receiver();
		
		// It also needs a channel, which can be pulled out of the Midi Voices enum in the Midi Manager.
		int droneChannel = MidiManager.MidiVoices.Drone.ChannelNumber;
		
		// Ask TheMidiOutParameterFactory to make us a PitchVelocity Parameter as specified and tell it to write automatically each tick.
		dronePitchVelOut = TheMidiOutParameterFactory.createPitchVelocityMidiOutParameter(
				middlePixelRBSumPitchScaleAdapter, droneVelocity, midiYoke1Rec, droneChannel, true);
		
		
		// **************************
		// Arpeggio - Pitch, Velocity
		// **************************
		// Here we want to link Output Adapters to the Arpeggio Pitch and Velocity parameters.
		// The pitch will come from the RGB Sum Scale Adapter.
		// The velocity will come from the RGB Sum High Pass Scale adapter (It'll only be heard if it passes the high pass (R+G+B > 150)
		//			A similar setup may be used in the final project to eliminate all "black" data.
		// We can reuse the receiver before, as they are Device (Midi Yoke 1) specific, not channel specific.
		// But we need a different channel.
		int arpChannel = MidiManager.MidiVoices.Arpeggio.ChannelNumber;
		
		// Ask TheMidiOutParameterFactory to make us a PitchVelocity Parameter as specified and tell it to write automatically each tick.
		arpPitchVelOut = TheMidiOutParameterFactory.createPitchVelocityMidiOutParameter(
				middlePixelRGBSumPitchScaleAdapter, middlePixelRGBSumHPVelocityScaleAdapter, midiYoke1Rec, arpChannel, true);
		
		
		// *************************
		// Harmony - Pitch, Velocity
		// *************************
		// Here we want to link Output Adapters to the Harmony Pitch and Velocity parameters.
		// The pitch will come from the RGB Average Scale Adapter.
		// The velocity will again be a constant of 60.
		Input harmVelocity = TheInputFactory.createConstant(60, false);
		
		// We can reuse the receiver again.
		// But we need the Harmony Channel.
		int harmChannel = MidiManager.MidiVoices.Harmony.ChannelNumber;
		
		// Ask TheMidiOutParameterFactory to make us a PitchVelocity Parameter as specified and tell it to write automatically each tick.
		harmPitchVelOut = TheMidiOutParameterFactory.createPitchVelocityMidiOutParameter(
				middlePixelRGBAvgPitchScaleAdapter, harmVelocity, midiYoke1Rec, harmChannel, true);
		
		
		// For debugging, we'll open a font for printing to the screen.
		// Load and set the font
		PFont font = loadFont("CourierNew36.vlw");
		textFont(font, 32);
	}

	
	public void Draw() {
		// Since everything is handled by the factories and Update Manager, we don't have to do anything here to get sound out. :D
		// But for debugging purposes, we'll print the values, and circle the middle pixel.
		
		
		fill(0, 0);
		// Draw a circle by the middle pixel
		ellipse(middlePixel.getXCoord(), middlePixel.getYCoord(), 10, 10);
		
		
		fill (255, 255);
		
		// Draw each color and value to the applet
		text("R = " + middlePixel.getRedInput().getValue(), 5, 30);
		text("G = " + middlePixel.getGreenInput().getValue(), 5, 60);
		text("B = " + middlePixel.getBlueInput().getValue(), 5, 90);
		text("T = " + middlePixelRGBSumMod.getValue(), 5, 120);
		text("D = " + middlePixelRBSumPitchScaleAdapter.getValue(), 5, 150);
		text("A = " + middlePixelRGBSumPitchScaleAdapter.getValue(), 5, 180);
		text("H = " + middlePixelRGBAvgPitchScaleAdapter.getValue(), 5, 210);
	}

	
	
	public int getFrameRate() {
		return 30;
	}

	public int getHeight() {
		return 480;
	}
	
	public int getWidth() {
		return 640;
	}

}
